﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;

namespace MarketSimulator.Utils.Maps
{
    public class MapBuilder
    {
        #region Constants

        /// <summary>
        /// Default map description file name.
        /// </summary>
        private const string DEFAULT_MAP_LOCATION = "default.map.description.xml";
        private const string CITIES_NODE_NAME = "cities";
        private const string CITY_NODE_NAME = "city";
        private const string ROUTES_NODE_NAME = "routes";
        private const string ROUTE_NODE_NAME = "route";
        private const string CITYA_ATTR = "cityA";
        private const string CITYB_ATTR = "cityB";
        private const string DISTANCE_ATTR = "distance";
        private const string STORAGE_NODE_NAME = "storage";
        private const string STORAGES_NODE_NAME = "storageUnits";
        private const string STORAGE_LOCATION_ID = "location";
        private const string STORAGE_CAPACITY = "capacity";
        private const string STORAGE_COST_PER_UNIT = "costPerUnit";

        #endregion

        #region Fields

        /// <summary>
        /// Map description XML file location
        /// </summary>
        public string MapDescriptionFile;

        #endregion

        #region Constructors

        public MapBuilder(string mapDescriptionFile)
        {
            MapDescriptionFile = mapDescriptionFile;
            
        }

        public MapBuilder() : this(DEFAULT_MAP_LOCATION) { }

        #endregion

        #region Public Methods

        /// <summary>
        /// Create a world map based on Map Description File
        /// </summary>
        /// <returns></returns>
        public void CreateWorldMap(string mapDescriptionFile = null)
        {
            if (!string.IsNullOrEmpty(mapDescriptionFile))
            {
                MapDescriptionFile = mapDescriptionFile;
            }

            ReadMapDescriptionFile();
        }

        #endregion

        #region Private Methods

        //Read the Map Description File
        private void ReadMapDescriptionFile()
        {
            if (!File.Exists(MapDescriptionFile))
            {
                throw new IOException("Map description XML file was not found.");
            }

            using (XmlReader reader = XmlReader.Create(MapDescriptionFile))
            {
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element && reader.Name == CITIES_NODE_NAME)
                    {
                        ReadCities(reader);
                    }
                    if (reader.NodeType == XmlNodeType.Element && reader.Name == ROUTES_NODE_NAME)
                    {
                        ReadRoutes(reader);
                    }
                    if (reader.NodeType == XmlNodeType.Element && reader.Name == STORAGES_NODE_NAME)
                    {
                        ReadStorageFacilities(reader);
                    }
                }
            }
        }

        /// <summary>
        /// Read the routes in the Routes member
        /// </summary>
        /// <param name="reader"></param>
        private void ReadRoutes(XmlReader reader)
        {
            while (reader.Read())
            {
                //If this is the end of routes nodes, return
                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == ROUTES_NODE_NAME)
                {
                    return;
                }
                else if (reader.NodeType == XmlNodeType.Element && reader.Name == ROUTE_NODE_NAME)
                {
                    long cityIdA = 0;
                    long cityIdB = 0;
                    long distance = long.MaxValue;
                    long.TryParse(reader.GetAttribute(CITYA_ATTR), out cityIdA);
                    long.TryParse(reader.GetAttribute(CITYB_ATTR), out cityIdB);
                    long.TryParse(reader.GetAttribute(DISTANCE_ATTR), out distance);

                    var cityA = WorldMap.GetCityById(cityIdA);
                    var cityB = WorldMap.GetCityById(cityIdB);
                    WorldMap.Routes.Add(new Route(cityA, cityB, distance));
                }
            }
        }

        /// <summary>
        /// Read cities nodes.
        /// </summary>
        /// <param name="reader"></param>
        private void ReadCities(XmlReader reader)
        {
            while (reader.Read())
            {
                //If this is the end of cities nodes, return
                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == CITIES_NODE_NAME)
                {
                    return;
                }
                else if (reader.NodeType == XmlNodeType.Element && reader.Name == CITY_NODE_NAME)
                {
                    long cityId = 0;
                    string cityName = string.Empty;
                    long.TryParse(reader.GetAttribute("id"), out cityId);
                    cityName = reader.GetAttribute("name");

                    City newCity = new City(cityId, cityName);
                    WorldMap.Cities.Add(newCity);
                }
            }
        }

        /// <summary>
        /// Read the storage nodes.
        /// </summary>
        /// <param name="reader"></param>
        private void ReadStorageFacilities(XmlReader reader)
        {
            while (reader.Read())
            {
                //If this is the end of cities nodes, return
                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == STORAGES_NODE_NAME)
                {
                    return;
                }
                else if (reader.NodeType == XmlNodeType.Element && reader.Name == STORAGE_NODE_NAME)
                {
                    long cityId = 0;
                    long capacity = 0;
                    double costPerUnit = 0;

                    long.TryParse(reader.GetAttribute(STORAGE_LOCATION_ID), out cityId);
                    long.TryParse(reader.GetAttribute(STORAGE_CAPACITY), out capacity);
                    double.TryParse(reader.GetAttribute(STORAGE_COST_PER_UNIT), out costPerUnit);

                    WorldMap.StorageFacilities.Add(new StorageFacility() { Location = cityId, TotalCapacity = capacity, CostPerUnit = costPerUnit });
                }
            }
        }

        #endregion
    }
}
